import { fetchWithRetry } from '@/frame/lib/fetch-utils'
import type { Response, NextFunction } from 'express'

import patterns from '@/frame/lib/patterns'
import { isArchivedVersion } from '@/archives/lib/is-archived-version'
import { setFastlySurrogateKey, SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key'
import { archivedCacheControl, defaultCacheControl } from '@/frame/middleware/cache-control'
import type { ExtendedRequest } from '@/types'

// This module handles requests for the CSS and JS assets for
// deprecated GitHub Enterprise versions by routing them to static content in
// one of the docs-ghes-<release number> repos.
// See also ./archived-enterprise-versions.ts for non-CSS/JS paths

export default async function archivedEnterpriseVersionsAssets(
  req: ExtendedRequest,
  res: Response,
  next: NextFunction,
) {
  // Only match asset paths
  // This can be true on /enterprise/2.22/_next/static/foo.css
  // or /_next/static/foo.css
  if (!patterns.assetPaths.test(req.path)) return next()

  // The URL is either in the format
  // /enterprise/2.22/_next/static/foo.css,
  // /enterprise-server@<release>,
  // or /_next/static/foo.css.
  // If the URL is prefixed with the enterprise version and release number
  // or if the Referrer contains the enterprise version and release number,
  // then we'll fetch it from the docs-ghes-<release number> repo.
  if (
    !(
      patterns.getEnterpriseVersionNumber.test(req.path) ||
      patterns.getEnterpriseServerNumber.test(req.path) ||
      patterns.getEnterpriseVersionNumber.test(req.get('referrer') || '') ||
      patterns.getEnterpriseServerNumber.test(req.get('referrer') || '')
    )
  ) {
    return next()
  }

  // Now we know the URL is definitely not /_next/static/foo.css
  // So it's probably /enterprise/2.22/_next/static/foo.css and we
  // should see if we might find this in the proxied backend.
  // But `isArchivedVersion()` will only return truthy if the
  // Referrer header also indicates that the request for this static
  // asset came from a page
  const { isArchived, requestedVersion } = isArchivedVersion(req)
  if (!isArchived || !requestedVersion) return next()

  // If this looks like a Next.js chunk or build manifest request from an archived page,
  // just return 204 No Content instead of trying to proxy it.
  // This suppresses noise from hydration requests that don't affect
  // content viewing since archived pages render fine server-side.
  // Only target specific problematic asset types, not all _next/static assets.
  if (
    (req.path.includes('/_next/static/chunks/') ||
      req.path.includes('/_buildManifest.js') ||
      req.path.includes('/_ssgManifest.js')) &&
    (req.get('referrer') || '').match(/enterprise(-server@|\/)[\d.]+/)
  ) {
    archivedCacheControl(res)
    setFastlySurrogateKey(res, SURROGATE_ENUMS.MANUAL)
    return res.sendStatus(204) // No Content - silently ignore
  }

  // In all of the `docs-ghes-<relase number` repos, the asset directories
  // are at the root. This removes the version and release number from the
  // asset path so that we can proxy the request to the correct location.
  const newEnterprisePrefix = `/enterprise-server@${requestedVersion}`
  const legacyEnterprisePrefix = `/enterprise/${requestedVersion}`
  const assetPath = req.path.replace(newEnterprisePrefix, '').replace(legacyEnterprisePrefix, '')

  // Just to be absolutely certain that the path can not contain
  // a URL that might trip up the GET we're about to make.
  if (
    assetPath.includes('../') ||
    assetPath.includes('://') ||
    (assetPath.includes(':') && assetPath.includes('@'))
  ) {
    defaultCacheControl(res)
    return res.status(404).type('text/plain').send('Asset path not valid')
  }

  const proxyPath = `https://github.github.com/docs-ghes-${requestedVersion}${assetPath}`
  try {
    const r = await fetchWithRetry(
      proxyPath,
      {},
      {
        retries: 0,
        throwHttpErrors: true,
      },
    )

    const body = await r.arrayBuffer()

    res.set('accept-ranges', 'bytes')
    const contentType = r.headers.get('content-type')
    if (contentType) {
      // Match got's behavior by adding charset=utf-8 to SVG files
      if (contentType === 'image/svg+xml') {
        res.set('content-type', `${contentType}; charset=utf-8`)
      } else {
        res.set('content-type', contentType)
      }
    }
    const contentLength = r.headers.get('content-length')
    if (contentLength) {
      res.set('content-length', contentLength)
    }
    res.set('x-is-archived', 'true')
    res.set('x-robots-tag', 'noindex')

    // This cache configuration should match what we do for archived
    // enterprise version URLs that are not assets.
    archivedCacheControl(res)
    setFastlySurrogateKey(res, SURROGATE_ENUMS.MANUAL)

    return res.send(Buffer.from(body))
  } catch (err) {
    // Primarily for the developers working on tests that mock
    // requests. If you don't set up `nock` correctly, you might
    // not realize that and think it failed for other reasons.
    if (err instanceof Error && err.toString().includes('Nock: No match for request')) {
      throw err
    }

    // It's important that we don't give up on this by returning a 404
    // here. It's better to let this through in case the asset exists
    // beyond the realm of archived enterprise versions.
    // For example, image you load
    // /enterprise-server@2.21/en/DOES/NOT/EXIST in your browser.
    // Quickly, we discover that the proxying is failing because
    // it didn't find a page called `/en/DOES/NOT/EXIST` over there.
    // So, we proceed to render *our* 404 HTML page.
    // Now, on that 404 page, it will reference static assets too.
    // E.g. <link href="/_next/static/styles.css">
    // These will thus be requested, with a Referrer header that
    // forces us to give it a chance, but it'll find it can't find it
    // but we mustn't return a 404 yet, because that
    // /_next/static/styles.css will probably still succeed because the 404
    // page is not that of the archived enterprise version.
    return next()
  }
}
